package com.gitee.l0km.javadocreader;

import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;

import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.DocumentationTool;
import javax.tools.DocumentationTool.DocumentationTask;
import javax.tools.ToolProvider;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitee.l0km.aocache.annotations.AoWeakCacheable;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;

import jdk.javadoc.doclet.DocletEnvironment;
import jdk.javadoc.doclet.Reporter;

/**
 * 读取指定java源文件的javadoc信息
 * @author guyadong
 *
 */
public class JavadocReader {
	protected static final Logger logger = LoggerFactory.getLogger(JavadocReader.class);
	private static String sourcepath;
	private static String[] sourcepathList = new String[0];
	private static String classpath;

	public static class Doclet implements jdk.javadoc.doclet.Doclet {

		volatile static DocletEnvironment root = null;

		@Override
	    public void init(Locale locale, Reporter reporter) {
	        logger.debug("Doclet using locale: " + locale);
	    }

	    @Override
	    public boolean run(DocletEnvironment docEnv) {
	    	root = docEnv;
	        return true;
	    }

	    @Override
	    public String getName() {
	        return "Doclet";
	    }

	    @Override
	    public Set<? extends Option> getSupportedOptions() {
	        Option[] options = {
	            new Option() {
	                private final List<String> someOption = List.of(
	                        "-Xdoclint"
	                );

	                @Override
	                public int getArgumentCount() {
	                    return 1;
	                }

	                @Override
	                public String getDescription() {
	                    return "an option with aliases";
	                }

	                @Override
	                public Option.Kind getKind() {
	                    return Option.Kind.STANDARD;
	                }

	                @Override
	                public List<String> getNames() {
	                    return someOption;
	                }

	                @Override
	                public String getParameters() {
	                    return "file";
	                }

	                @Override
	                public boolean process(String opt, List<String> arguments) {
	                    return true;
	                }
	            }
	        };

	        return Set.of(options);
	    }

		@Override
	    public SourceVersion getSupportedSourceVersion() {
	        // support the latest release
	        return SourceVersion.latest();
	    }
	}
	public static void show(){
		if(null == Doclet.root){
			return ;
		}
        Set<TypeElement> classes = ElementFilter.typesIn(Doclet.root.getIncludedElements());
        for (TypeElement typeElement:classes) {
        	new ExtClassDoc(Doclet.root,typeElement).output(System.out);
        }
	}
	private static DocletEnvironment getRoot() {
		if(null == Doclet.root) {
			synchronized (JavadocReader.class) {
				if(null == Doclet.root) {
					readDocs(null, classpath, sourcepath);
				}
			}
		}
		return Doclet.root;
	}

	public static void resetRoot() {
		Doclet.root = null;
	}

	/**
	 * 解析指定的java源文件返回 {@link DocletEnvironment}对象<br>
	 * 参见 <a href="https://docs.oracle.com/javase/9/tools/javadoc.htm">javadoc</a>
	 * @param source a java source file or package name
	 * @param classpath value for  '-classpath',{@code source}的class位置,可为{@code null},如果不提供,无法获取到完整的注释信息(比如annotation)
	 * @param sourcepath value for '-sourcepath'
	 * @return {@link DocletEnvironment}对象
	 */
	public synchronized static DocletEnvironment readDocs(String source, String classpath,String sourcepath) {
		List<String> args = Lists.newArrayList(
				"-quiet","-Xmaxerrs","1","-Xmaxwarns","1","-encoding","utf-8","-private","-Xdoclint",
				"none");
		if(Strings.isNullOrEmpty(source) || !source.endsWith(".java")) {
			/** source参数为包名或为空时，指定-subpackages参数,获取所有类注解 */
			args.add("-subpackages");
			args.add(subpackages(sourcepath));
		}
		if(!Strings.isNullOrEmpty(classpath)){
			args.add("-classpath");
			args.add(normailzePathSeparator(classpath));
		}
		if(!Strings.isNullOrEmpty(sourcepath)){
			args.add("-sourcepath");
			args.add(normailzePathSeparator(sourcepath));
		}
		if(!Strings.isNullOrEmpty(source)) {
			args.add(source);
		}
		logger.debug("Javadoc Options:\n<<<<\n{}\n>>>>",Joiner.on("\n").join(args));
		// 获取 DocumentationTool 实例
		DocumentationTool docTool = ToolProvider.getSystemDocumentationTool();
		DocumentationTask task = docTool.getTask(new PrintWriter(System.out), null, null, Doclet.class, args, null);
		boolean sucessed = false;
		try {
			sucessed = task.call();
		} catch (RuntimeException e) {
			throw new IllegalStateException();
		}
		if(!sucessed) {
			throw new IllegalStateException();
		}
		return Doclet.root;
	}
	/**
	 * 读取{@code source}指定源文件的注释
	 * @param source
	 * @param classpath
	 * @param sourcepath
	 * @return 读取失败返回{@code null}
	 */
	public synchronized static ExtClassDoc read(String source, String classpath,String sourcepath) {
		try {
			readDocs(source, classpath, sourcepath);
			return new ExtClassDoc(Doclet.root,ElementFilter.typesIn(Doclet.root.getIncludedElements()).iterator().next());	
		} catch (IllegalStateException e) {
			return null;
		}catch (Exception e) {
			e.printStackTrace();
			return null;
		}		
	}
	/** @see #read(String, String) */
	public static ExtClassDoc read(String source) {
		return read(source,classpath);
	}
	public static ExtClassDoc read(String prefix,Class<?> clazz) {
		return read(getSourceFile(prefix,clazz),classpath);
	}

	private static TypeElement classNamed(String classname) {
		return ElementFilter.typesIn(getRoot().getIncludedElements()).stream()
				.filter(e -> e.getQualifiedName().contentEquals(classname)).findFirst().orElse(null);
	}

	/**
	 * 根据类名查找注释对象
	 * @param qualifiedName 带包名的类全名
	 */
	@AoWeakCacheable
	private static ExtClassDoc readQualifiedName(String qualifiedName) {
		if (null != qualifiedName) {
			try {
				if (getRoot() == null) {
					return null;
				}
				TypeElement classDoc = classNamed(qualifiedName);
				if (null != classDoc) {
					return new ExtClassDoc(getRoot(),classDoc);
				}
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}
		return null;
	}
	@AoWeakCacheable
	private static ExtClassDoc read0(Class<?> clazz) {
		if (clazz == null) {
			return null;
		}
		String qualifiedName = clazz.getName().replace('$', '.');
		return readQualifiedName(qualifiedName);
	}
	public static ExtClassDoc read(Class<?> clazz) {
		return read0(clazz);
	}
	public static ExtClassDoc read(String source, List<String> classpath) {
		return read(source,null == classpath?null:Joiner.on(File.pathSeparator).skipNulls().join(classpath));
	}
	public static ExtClassDoc read(String source, String[] classpath) {
		return read(source,null == classpath?null:Joiner.on(File.pathSeparator).skipNulls().join(classpath));
	}
	public synchronized static ExtClassDoc read(String source, String classpath) {
		return read(source,classpath,sourcepath);
	}
	public static ExtClassDoc read(String source, List<String> classpath,List<String> sourcepath) {
		return read(source,
				null == classpath ? null : Joiner.on(File.pathSeparator).skipNulls().join(classpath),
				null == sourcepath ? null : Joiner.on(File.pathSeparator).skipNulls().join(sourcepath));
	}
	public static ExtClassDoc read(String source, String[] classpath,String[] sourcepath) {
		return read(source,
				null == classpath ? null : Joiner.on(File.pathSeparator).skipNulls().join(classpath),
				null == sourcepath ? null : Joiner.on(File.pathSeparator).skipNulls().join(sourcepath));
	}
	/**
	 * 返回类的源文件位置
	 * @param prefix 源文件夹,可为{@code null}
	 * @param clazz 为{@code null}则返回{@code null}
	 * @return 返回'/'分隔'.java'结尾的路径名,for example, '/home/src/java/awt/Graphics*java‘
	 */
	public static String getSourceFile(String prefix,Class<?> clazz){
		if(null != clazz ){
			Class<?> innerClass = clazz;
			Class<?> declaringClass = innerClass.getDeclaringClass();
			while(declaringClass != null){
				innerClass = declaringClass;
				declaringClass = declaringClass.getDeclaringClass();
			}
			String source = innerClass.getName().replace('.', File.separatorChar) + ".java";
			return prefix == null ? source : prefix + File.separator + source;
		}
		return null;
	}

	/**
	 * 在{@link #setSourcepath(String)}指定的源码路径中搜索类的源码文件
	 * @param clazz
	 * @return source file path or null if clazz is null or not found  
	 */
	public static String findSourceFileInSourcepathList(Class<?> clazz){
		if(null == clazz) {
			return null;
		}
		for(String path:sourcepathList){
			String source = getSourceFile(path, clazz);
			if(new File(source).isFile()){
				return source;
			}
		}
		return null;
	}
	/**
	 * 返回是否存在指定类的源码,{@code clazz}为{@code null}返回{@code null}
	 * @param clazz
	 * @see #findSourceFileInSourcepathList(Class)
	 */
	public static boolean existSourceOf(Class<?> clazz) {
		String file = findSourceFileInSourcepathList(clazz);
		return null != file;
	}
	/**
	 * 返回类的源码位置路径
	 * @param clazz
	 * @return file path or null if clazz is null
	 * @see #getSourceFile(String, Class)
	 */
	public static String getSourceFile(Class<?> clazz){
		if(null == clazz) {
			return null;
		}
		String found = findSourceFileInSourcepathList(clazz);
		if(null != found) {
			return found;
		}
		return getSourceFile(null, clazz);
	}
	
	/**
	 * @return sourcepath
	 */
	public static String getSourcepath() {
		return sourcepath;
	}
	/**
	 * @param sourcepath 要设置的 sourcepath
	 */
	public static void setSourcepath(String sourcepath) {
		JavadocReader.sourcepath = sourcepath;
		JavadocReader.sourcepathList = asPaths(sourcepath).toArray(new String[0]);
	}
	/**
	 * @return classpath
	 */
	public static String getClasspath() {
		return classpath;
	}
	/**
	 * @param classpath 要设置的 classpath
	 */
	public static void setClasspath(String classpath) {
		JavadocReader.classpath = classpath;
	}
	
	/**
	 * 归一化输入的路径列表中的路径分隔符<br>
	 * 则将字符串中的';,:'替换为系统路径分割符({@link File#pathSeparator})
	 * @param input ';,:'分割的路径列表
	 * @return 返回归一化的字符串,输入为{@code null}则返回 {@code null}
	 */
	private static String normailzePathSeparator(String input) {
		return Joiner.on(File.pathSeparator).join(asPaths(input));
	}
	/**
	 * 将{@code input}用{@code delim}指定的分隔符切分为不含空格和分隔符的一组字符串
	 * @param input 输入字符串
	 * @param delim 包含多个分隔符的字符串
	 * @return {@code input}或{@code delim}为{@code null}时返回空表
	 */
	private static final List<String> elementsOf(String input,String delim) {
		List<String> list = new ArrayList<String>();
		if (input != null && delim != null) {
			StringTokenizer st = new StringTokenizer(input, delim);
			while (st.hasMoreTokens()) {
				list.add(st.nextToken());
			}
		}
		return list;
	}
	/**
	 * 将';,:'分割的字符串拆分为路径列表<br>
	 * @param input ';,:'分割的路径列表
	 * @return 返回字符串列表,输入为{@code null}则返回空表
	 */
	private static List<String> asPaths(String input) {
		if(!Strings.isNullOrEmpty(input)){
			List<String> paths;
			if (File.pathSeparatorChar == ':') {
				paths = elementsOf(input,";,:");
			}else {
				paths = elementsOf(input,";,");
			}
			return FluentIterable.from(paths).transform(String::trim).toList();
		}
		return Collections.emptyList();
	}

	private static String subpackages(String sourcepath) {
		FluentIterable<String> subpackages = FluentIterable.of();
		for (String p : asPaths(sourcepath)) {
			File path = new File(p);
			if (path.isDirectory()) {
				subpackages = subpackages.append(path.list((dir, name) -> new File(dir, name).isDirectory()));
			}
		}
		// 包名冒号分割
		// 参见
		// https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDJEDJI
		return Joiner.on(":").join(subpackages.copyInto(new TreeSet<>()));

	}
}
